package org.codefilarete.stalactite.sql.statement.binder;

import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.sql.Blob;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;

import static org.codefilarete.stalactite.sql.statement.binder.PreparedStatementWriter.ofMethodReference;

/**
 * Default {@link PreparedStatementWriter}s mapped to methods of {@link PreparedStatement}
 * 
 * @author Guillaume Mary
 */
public final class DefaultPreparedStatementWriters {
	
	/* Implementation note: although primitive binders are parameterized with generics they are not wrapped by a NullAwarePreparedStatementWriter
	 * so they'll throw an exception or convert Object-typed value passed if null is writen or read.
	 */
	
	/** Writer straightly bound to {@link PreparedStatement#setLong(int, long)} */
	public static final PreparedStatementWriter<Long> SET_LONG_WRITER = ofMethodReference(PreparedStatement::setLong);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setLong(int, long)}.
	 */
	public static final PreparedStatementWriter<Long> LONG_PRIMITIVE_WRITER = new NullSafeguardPreparedStatementWriter<>(SET_LONG_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setLong(int, long)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Long> LONG_WRITER = new NullAwarePreparedStatementWriter<>(SET_LONG_WRITER);
	
	/** Writer straightly bound to {@link PreparedStatement#setInt(int, int)} */
	private static final PreparedStatementWriter<Integer> SET_INT_WRITER = ofMethodReference(PreparedStatement::setInt);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setInt(int, int)}.
	 */
	public static final PreparedStatementWriter<Integer> INTEGER_PRIMITIVE_WRITER = new NullSafeguardPreparedStatementWriter<>(SET_INT_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setInt(int, int)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Integer> INTEGER_WRITER = new NullAwarePreparedStatementWriter<>(SET_INT_WRITER);
	
	/** Writer straightly bound to {@link PreparedStatement#setByte(int, byte)} */
	private static final PreparedStatementWriter<Byte> SET_BYTE_WRITER = ofMethodReference(PreparedStatement::setByte);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setByte(int, byte)}.
	 */
	public static final PreparedStatementWriter<Byte> BYTE_PRIMITIVE_WRITER = new NullSafeguardPreparedStatementWriter<>(SET_BYTE_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setByte(int, byte)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Byte> BYTE_WRITER = new NullAwarePreparedStatementWriter<>(SET_BYTE_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBytes(int, byte[])}.
	 */
	public static final PreparedStatementWriter<byte[]> BYTES_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setBytes);
	
	/** Writer straightly bound to {@link PreparedStatement#setDouble(int, double)} */
	private static final PreparedStatementWriter<Double> SET_DOUBLE_WRITER = ofMethodReference(PreparedStatement::setDouble);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setDouble(int, double)}.
	 */
	public static final PreparedStatementWriter<Double> DOUBLE_PRIMITIVE_WRITER = new NullSafeguardPreparedStatementWriter<>(SET_DOUBLE_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setDouble(int, double)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Double> DOUBLE_WRITER = new NullAwarePreparedStatementWriter<>(SET_DOUBLE_WRITER);
	
	/** Writer straightly bound to {@link PreparedStatement#setFloat(int, float)} */
	private static final PreparedStatementWriter<Float> SET_FLOAT_WRITER = ofMethodReference(PreparedStatement::setFloat);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setFloat(int, float)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Float> FLOAT_PRIMITIVE_WRITER = new NullSafeguardPreparedStatementWriter<>(SET_FLOAT_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setFloat(int, float)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Float> FLOAT_WRITER = new NullAwarePreparedStatementWriter<>(SET_FLOAT_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBigDecimal(int, BigDecimal)}.
	 */
	public static final PreparedStatementWriter<BigDecimal> BIGDECIMAL_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setBigDecimal);
	
	/** Writer straightly bound to {@link PreparedStatement#setBoolean(int, boolean)} */
	private static final PreparedStatementWriter<Boolean> SET_BOOLEAN_WRITER = ofMethodReference(PreparedStatement::setBoolean);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBoolean(int, boolean)}.
	 */
	public static final PreparedStatementWriter<Boolean> BOOLEAN_PRIMITIVE_WRITER = new NullSafeguardPreparedStatementWriter<>(SET_BOOLEAN_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBoolean(int, boolean)}.
	 * Wrapped into a {@link NullAwarePreparedStatementWriter} to manage type boxing and unboxing.
	 */
	public static final PreparedStatementWriter<Boolean> BOOLEAN_WRITER = new NullAwarePreparedStatementWriter<>(SET_BOOLEAN_WRITER);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setDate(int, Date)}.
	 * For common usage, prefer {@link DateBinder} because it uses {@link java.util.Date}
	 * @see DateBinder
	 */
	public static final PreparedStatementWriter<Date> DATE_SQL_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setDate);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setTimestamp(int, Timestamp)}.
	 */
	public static final PreparedStatementWriter<Timestamp> TIMESTAMP_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setTimestamp);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setString(int, String)}.
	 */
	public static final PreparedStatementWriter<String> STRING_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setString);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBinaryStream(int, InputStream)}.
	 */
	public static final PreparedStatementWriter<InputStream> BINARYSTREAM_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setBinaryStream);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBlob(int, Blob)}.
	 */
	public static final PreparedStatementWriter<Blob> BLOB_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setBlob);
	
	/**
	 * {@link PreparedStatementWriter} for {@link PreparedStatement#setBlob(int, InputStream)}.
	 */
	public static final PreparedStatementWriter<InputStream> BLOB_INPUTSTREAM_WRITER = new NullAwarePreparedStatementWriter<>(PreparedStatement::setBinaryStream);
	
	/**
	 * {@link PreparedStatementWriter} for {@link java.util.Date}
	 */
	public static final PreparedStatementWriter<java.util.Date> DATE_WRITER = new NullAwarePreparedStatementWriter<>(new DateBinder());
	
	/**
	 * {@link PreparedStatementWriter} for {@link java.time.LocalDate}
	 */
	public static final PreparedStatementWriter<LocalDate> LOCALDATE_WRITER = new NullAwarePreparedStatementWriter<>(new LocalDateBinder());
	
	/**
	 * {@link PreparedStatementWriter} for {@link java.time.LocalDateTime}
	 */
	public static final PreparedStatementWriter<LocalDateTime> LOCALDATETIME_WRITER = new NullAwarePreparedStatementWriter<>(new LocalDateTimeBinder());
	
	/**
	 * {@link PreparedStatementWriter} for {@link UUID}
	 */
	public static final PreparedStatementWriter<UUID> UUID_WRITER = new NullAwarePreparedStatementWriter<>(new UUIDParameterBinder());
	
	/**
	 * {@link PreparedStatementWriter} for {@link Path}
	 */
	public static final PreparedStatementWriter<Path> PATH_WRITER = new NullAwarePreparedStatementWriter<>(new PathBinder());
	
	/**
	 * {@link PreparedStatementWriter} for {@link Number}. Bound to {@link PreparedStatement#setDouble(int, double)}
	 */
	public static final PreparedStatementWriter<Number> NUMBER_WRITER = new PreparedStatementWriter<Number>() {
		@Override
		public void set(PreparedStatement preparedStatement, int valueIndex, Number value) throws SQLException {
			preparedStatement.setDouble(valueIndex, value.doubleValue());
		}
		
		@Override
		public Class<Number> getType() {
			return Number.class;
		}
	};
	
	private DefaultPreparedStatementWriters() {
		// Class for constants
	}
}
